{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# <center>第11章 Web的概念与原理</center>\n",
    "\n",
    "<br>\n",
    "\n",
    "- [11.1 Web 概念与开发技术](#11.1-Web-概念与开发技术)\n",
    "  - [11.1.1 Web 的概念](#11.1.1-Web-的概念)\n",
    "  - [11.1.2 Web 页面的访问过程](#11.1.2-Web-页面的访问过程)\n",
    "  - [11.1.3 Web 开发技术栈](#11.1.3-Web-开发技术栈)\n",
    "- [11.2 统一资源标识符](#11.2-统一资源标识符)\n",
    "  - [11.2.1 统一资源定位符](#11.2.1-统一资源定位符)\n",
    "  - [11.2.2 URL 的解析](#11.2.2-URL-的解析)\n",
    "- [11.3 超文本标记语言](#11.3-超文本标记语言)\n",
    "  - [11.3.1 HTML的结构](#11.3.1-HTML的结构)\n",
    "  - [11.3.2 HTML 文档的修饰与控制](#11.3.2-HTML-文档的修饰与控制)\n",
    "- [11.4 超文本传输协议](#11.4-超文本传输协议)\n",
    "  - [11.4.1 HTTP 请求](#11.4.1-HTTP-请求)\n",
    "  - [11.4.2 HTTP 响应](#11.4.2-HTTP-响应)\n",
    "  - [11.4.3 HTTP 协议解析](#11.4.3-HTTP-协议解析)\n",
    "- [11.5 Web 服务器的工作原理](#11.5-Web-服务器的工作原理)\n",
    "  - [11.5.1 基于套接字的 Web 服务器端](#11.5.1-基于套接字的-Web-服务器端)\n",
    "  - [11.5.2 简单Web服务器](#11.5.2-简单Web服务器)\n",
    "- [11.6 Web 客户端的工作原理](#11.6-Web-客户端的工作原理)\n",
    "  - [11.6.1 基于套接字的 Web 客户端](#11.6.1-基于套接字的-Web-客户端)\n",
    "  - [11.6.2 基于 http.client 的 Web 客户端](#11.6.2-基于-http.client-的-Web-客户端)\n",
    "  - [11.6.3 urllib.request与requests](#11.6.3-urllib.request与requests)\n",
    "- [11.7 WebSocket 协议 *](#11.7-WebSocket-协议-*)\n",
    "  - [11.7.1 WebSocket 的工作过程](#11.7.1-WebSocket-的工作过程)\n",
    "  - [11.7.2 握手](#11.7.2-握手)\n",
    "  - [11.7.3 WebSocket 协议解析](#11.7.3-WebSocket-协议解析)\n",
    "  - [11.7.4 WebSocket 服务器](#11.7.4-WebSocket-服务器)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.1 Web 概念与开发技术\n",
    "\n",
    "### 11.1.1 Web 的概念\n",
    "- Web\n",
    "  - 是万维网（World Wide Web）的简称\n",
    "  - 是一个通过互联网（Internet）进行访问的由大量相互链接的超文件组成的系统\n",
    "  - 是一个客户机/服务器模式的分布式系统\n",
    "    - 客户端：浏览器、移动APP\n",
    "      - 浏览器/服务器\n",
    "    - 服务器端\n",
    "- Web核心技术\n",
    "  - HTML：用于定义超文本文档的结构和格式\n",
    "  - URI：用于唯一的定位Web中的每个资源，包括文档、图片、视频、声音等等\n",
    "  - HTTP：用于规定客户端和服务器之间的交互、通信标准"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "  - Web 2.0\n",
    "    - Web的应用趋于交互化、社交化\n",
    "    - 以互动、分享和关系为核心\n",
    "    - 典型的Web 2.0 应用包括博客、微博客、短视频、百科站点、社交网络、即时通讯等\n",
    "    - 影响\n",
    "      - 巨大的业务数据\n",
    "      - 深深改变了人们的生活方式\n",
    "      - 巨大的商业价值\n",
    "    - 技术特点\n",
    "      - Web 2.0本质上并有引入革命性的技术，其核心依旧是HTML、URI和HTTP协议\n",
    "      - 服务器端应用程序起着更为核心的作用，Web页面不再是静态的\n",
    "      - 要求强大的数据库系统和业务逻辑系统的支持，与传统软件的应用服务器越来越相似\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "  - Web 3.0\n",
    "    - 超高的带宽\n",
    "    - 人工智能技术的应用\n",
    "    - 云计算\n",
    "    - 语义网技术\n",
    "    - 虚拟现实/增强现实"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- Web的工作原理\n",
    "\n",
    "<br><br>\n",
    "<center>\n",
    "  <img src='./figures/fig11-1.png' width=\"60%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 11.1.2 Web 页面的访问过程\n",
    "\n",
    "- 静态网页的运行过程\n",
    "\n",
    "<br><br>\n",
    "\n",
    "<center>\n",
    "  <img src=\"./figures/fig11-2.png\" width=\"50%\"/>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 动态网页的运行过程\n",
    "\n",
    "<br>\n",
    "\n",
    "<center>\n",
    "  <img src=\"./figures/fig11-3.png\" width=\"75%\"/>\n",
    "</center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.1.3 Web 开发技术栈\n",
    "<br>\n",
    "\n",
    "<center>\n",
    "  <img src=\"./figures/fig11-4.png\" width=\"75%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.2 统一资源标识符\n",
    "\n",
    "- URI\n",
    "  - 全称是通一资源标识符（Uniform Resource Identifie），用来唯一地标识一个资源\n",
    "  - 不要求其所标识的资源是网络资源\n",
    "- URL\n",
    "  - 统一资源定位符（Uniform Resource Locator，URL）\n",
    "- URN\n",
    "  - 统一资源名称（Uniform Resource Name，URN）\n",
    "  - 利用命名空间来确保URN的合局唯一性，其形式为`urn:<NID>:<NSS>`\n",
    "  - 例：`urn:isbn:0451450523`，表示一本书的编号"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.2.1 统一资源定位符\n",
    "\n",
    "- URL的标准格式\n",
    "  - 端口号可以省略，默认取值为80；文件名、查询、片段ID都是可选项\n",
    "\n",
    "```\n",
    "[协议类型]://[服务器地址]:[端口号]/[资源层级路径][文件名]?[查询]#[片段ID]\n",
    "```\n",
    "\n",
    "- 例：\n",
    "\n",
    "```\n",
    "http://www.exmaple.com:9000/path/contents?key1=value1&key2=value2#anchorid\n",
    "```\n",
    "\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.2.2 URL 的解析\n",
    "\n",
    "#### `urllib.parse`\n",
    "\n",
    "- `urllib.parse`模块提供了解析或构造URL的功能\n",
    "  - `urlparse`\n",
    "  - `urlunparse`\n",
    "  - `urljoin`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'http'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from urllib.parse import urlparse, parse_qs\n",
    "url = 'http://www.exmaple.com:9000/path/contents?key1=value1&key2=value21&key2=value22#anchorid'\n",
    "rst = urlparse(url)\n",
    "rst.scheme"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'www.exmaple.com'"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.hostname"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "9000"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.port"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'www.exmaple.com:9000'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.netloc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'/path/contents'"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'key1=value1&key2=value21&key2=value22'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.query"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'anchorid'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "rst.fragment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'key1': ['value1'], 'key2': ['value21', 'value22']}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "parse_qs(rst.query)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `urlunparse`的功能与`urlparse`相反，是将各个组成部分组装为一个合法的URL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('http',\n",
       " 'www.exmaple.com:9000',\n",
       " '/path/contents',\n",
       " '',\n",
       " 'key1=value1&key2=value21&key2=value22',\n",
       " 'anchorid')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from urllib.parse import urlunparse\n",
    "tuple(rst)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'http://www.exmaple.com:9000/path/contents?key1=value1&key2=value21&key2=value22#anchorid'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "urlunparse(tuple(rst))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `urljoin`的作用是将原URL中的服务器根路径与一个新的路径合并为一个新的URL"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'http://www.exmaple.com:9000/path1/contents1'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from urllib.parse import urljoin\n",
    "urljoin(url, '/path1/contents1')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'http://www.exmaple.com:9000/path/path1/contents1'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "urljoin(url, 'path1/contents1')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.3 超文本标记语言\n",
    "\n",
    "### 11.3.1 HTML的结构\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- HTML示例(eg_11-01)\n",
    "```html\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "<head>\n",
    "    <meta charset=\"UTF-8\">\n",
    "    <title>Python编程</title>\n",
    "</head>\n",
    "<body>\n",
    "    <div class=\"continer\">\n",
    "        <div class=\"box\">\n",
    "            <h1>课后作业提交</h1>\n",
    "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
    "                <div>\n",
    "                    <span>学号：</span><input type=\"text\" name=\"usertag\" />\n",
    "                    <span></span>\n",
    "                    <span>密码：</span><input type=\"password\" name=\"password\" />\n",
    "                </div>\n",
    "                <br>\n",
    "                <div>\n",
    "                    <textarea name=\"code\" id=\"codebox\" rows=\"10\" wrap=\"off\">\n",
    "                        代码拷到这里！\n",
    "                    </textarea>\n",
    "                </div>\n",
    "                <br>\n",
    "                <div>\n",
    "                    <input type=\"submit\" value=\"提交代码\" />\n",
    "                </div>\n",
    "            </form>\n",
    "        </div>\n",
    "        <br>\n",
    "    </div>\n",
    "</body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 常用 HTML 标签\n",
    "\n",
    "|标签|功能|常用子标签|\n",
    "|:--|:--|:--|\n",
    "|`html` |HTML 文档的最外层标签 |`head`, `body`|\n",
    "|`head` |HTML 文档的头部 |`title`, `link`, `script`, `style`, `meta`|\n",
    "|`body` |HTML 文档的主体 |`head` 之外的大多数标签|\n",
    "|`title` |HTML 文档的标题 |无|\n",
    "|`talbe` |表格 |`tr`, `td`|\n",
    "|`tr` |表格的一行 |`td`|\n",
    "|`td` |表格的一个单元格 |大多数能放入 `body` 的标签|\n",
    "|`form` |表单 |`input`、`select` 等输入控件，`table`, `div`|\n",
    "|`textarea` |文本域 |——|\n",
    "|`div` |块元素，用于组织文档内容 |大多数能放入 `body `的标签|\n",
    "|`span` |行内块元素 |`a`|\n",
    "|`ul` |无序列表 |`li`|\n",
    "|`ol` |有序列表 |`li`|\n",
    "|`li` |列表元素 |`a`, `span`|\n",
    "|`a` |超链接 |——|\n",
    "|`img` |图片 |——|\n",
    "|`br` |换行 |——|\n",
    "|`h1`-`h6` |六个级别的标题 |`a`,` span`|\n",
    "|`link` |用于链接外部样式表 |——|\n",
    "|`style` |内部样式表 |——|\n",
    "|`script` |JavaScript 脚本 |——|\n",
    "|`audio` |音频内容 |——|\n",
    "|`video` |视频内容 |——|\n",
    "|`canvas` |图形容器 |——|"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 页面显示如下\n",
    "\n",
    "<br><br>\n",
    "\n",
    "<center>\n",
    "<img src=\"./figures/fig11-5.png\" width=\"60%\"/>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.3.2 HTML 文档的修饰与控制\n",
    "\n",
    "<br><br>\n",
    "\n",
    "<center>\n",
    "<img src=\"./figures/fig11-x1.png\" width=\"60%\"/>\n",
    "</center>\n",
    "\n",
    "- HTML样式控制\n",
    "  - 层叠样式表（Cascading Style Sheets，CSS）\n",
    "  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 例 11-2\n",
    "\n",
    "\n",
    "```html\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "<head>\n",
    "    <meta charset=\"UTF-8\">\n",
    "    <title>Python编程</title>\n",
    "    <style>\n",
    "        .continer {\n",
    "            margin: 0 auto;\n",
    "            width: 80%;\n",
    "        }\n",
    "        .box {\n",
    "            margin: 0 auto;\n",
    "            width: 100%;\n",
    "            text-align: center;\n",
    "        }\n",
    "    </style>\n",
    "</head>\n",
    "<body>\n",
    "    <div class=\"continer\">\n",
    "        <div class=\"box\">\n",
    "            <h1>课后作业提交</h1>\n",
    "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
    "                <div>\n",
    "                    <span>学号：</span>\n",
    "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
    "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
    "                    <span>密码：</span>\n",
    "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
    "                </div>\n",
    "                <br>\n",
    "                <div>\n",
    "                    <textarea name=\"code\" id=\"codebox\" rows=\"10\" wrap=\"off\"\n",
    "                          style=\"width: 100%; overflow-y:scroll; overflow:scroll;\">\n",
    "                        代码拷到这里！\n",
    "                    </textarea>\n",
    "                </div>\n",
    "                <br>\n",
    "                <div style=\"margin: 0 auto; text-align: center;\">\n",
    "                    <input type=\"submit\" value=\"提交代码\" \n",
    "                         style=\"width:100px; line-height:99px;display:inline-block\" />\n",
    "                </div>\n",
    "            </form>\n",
    "        </div>\n",
    "    </div>\n",
    "</body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "页面显示为：\n",
    "\n",
    "<br>\n",
    "\n",
    "<center>\n",
    "<img src=\"./figures/fig11-6a.png\" width=\"60%\"/>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- HTML行为控制\n",
    "  - JavaScript"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "- 例 11-3\n",
    "\n",
    "```html\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "<head>\n",
    "    ... \n",
    "    <script type=\"text/javascript\">\n",
    "        function clear_code(id) {\n",
    "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
    "                document.getElementById(id).value = \"\";\n",
    "            }\n",
    "        }\n",
    "    </script>\n",
    "</head>\n",
    "<body>\n",
    "    <div class=\"continer\">\n",
    "        <div class=\"box\">\n",
    "            <h1>课后作业提交</h1>\n",
    "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
    "                ...\n",
    "                <div>\n",
    "                    <textarea name=\"code\" id=\"codebox\" rows=\"10\" wrap=\"off\"\n",
    "                        style=\"width: 100%; overflow-y:scroll; overflow:scroll;\"\n",
    "                        onclick=\"clear_code(this.id)\">\n",
    "                        代码拷到这里！\n",
    "                    </textarea>\n",
    "                </div>\n",
    "                ...\n",
    "            </form>\n",
    "        </div>\n",
    "    </div>\n",
    "</body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "页面显示为：\n",
    "\n",
    "<br>\n",
    "\n",
    "<center>\n",
    "<img src=\"./figures/fig11-6b.png\" width=\"60%\"/>\n",
    "</center>\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.4 超文本传输协议\n",
    "\n",
    "- HTTP\n",
    "  - Web服务器与客户端之间的传输HTML文档的协议\n",
    "  - 处于OSI/ISO参考模型的最顶层应用层，在TCP/IP参考模型中位于传输层之上，通常基于TCP协议实现\n",
    "  \n",
    "<br>\n",
    "<center>\n",
    "<img src=\"./figures/fig11-7.png\" width=\"40%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- HTTP协议的特点\n",
    "  - 无连接性：一般情况下，为了节省传输时间降低网络开销，每次TCP连接只处理一个请求/响应过程，客户端接收到服务器的响应好TCP连接就被断开\n",
    "  - 无状态性：无状态是指协议没有对事务处理的记忆能力，服务器需要使用其他的手段来判断不同请求是否来自相同的客户端\n",
    "  - 媒体内容独立性：HTTP协议并不要求传输内容一定是HTML文档，实际上任何数据都可以通过HTTP协议传输，传输内容的类型和格式由MIME类型来指定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.4.1 HTTP 请求\n",
    "\n",
    "#### HTTP请求的格式\n",
    "\n",
    "<br>\n",
    "<center>\n",
    "<img src=\"./figures/fig11-8.png\" width=\"50%\"/><br>\n",
    "  HTTP请求格式\n",
    "</center>\n",
    "\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 请求方法\n",
    "\n",
    "| 方法    | 描述                                                         |\n",
    "| :------ | :----------------------------------------------------------- |\n",
    "| GET     | 请求服务器中的资源，请求的查询参数包含在URL之中              |\n",
    "| HEAD    | 类似于 GET 请求，只不过返回的响应中没有具体的内容，用于获取请求头中的信息，通常常并不单独使用 |\n",
    "| POST    | 向指定资源提交数据进行处理请求（例如提交表单或者上传文件），数据包含在请求正文之中。POST 请求往往意味着资源的创建或修改 |\n",
    "| PUT     | 从客户端向服务器传送的数据取代指定的文档的内容               |\n",
    "| DELETE  | 请求服务器删除指定的资源                                     |\n",
    "| PATCH   | 是对 PUT 方法的补充，用来对已知资源进行局部更新              |\n",
    "| CONNECT | 将服务器作为代理来访问其他Web资源                            |\n",
    "| OPTIONS | 查看服务器的性能                                             |\n",
    "| TRACE   | 回溯服务器收到的请求，主要用于测试或诊断                     |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 协议版本\n",
    "\n",
    "- HTTP 1.0\n",
    "  - 支持GET、POST和HEAD三种方法；传输内容不再仅限于HTML文档，可以利用Content-Type支持多种格式；支持浏览器缓存；\n",
    "- HTTP 1.1\n",
    "  - 引入了持久连接（Persistent Connection），即一次TCP连接可以被多个HTTP请求重复使用；新增了多种请求方法类型；\n",
    "- HTTP 2.0\n",
    "  - 增加了双工模式，即客户端同时发出多个请求的同时，服务器端也可以同时处理多个请求；增加了服务器推送功能。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### HTTP请求头\n",
    "\n",
    "| Header          | 解释                                                         | 示例                                                        |\n",
    "| :-------------- | :----------------------------------------------------------- | :---------------------------------------------------------- |\n",
    "| Accept          | 客户端能够接收的内容类型                                     | Accept: text/plain, text/html                               |\n",
    "| Accept-Charset  | 客户端能够接受的字符编码集                                   | Accept-Charset: utf-8                                       |\n",
    "| Accept-Encoding | 客户端能够支持的内容压缩编码类型                             | Accept-Encoding: compress, gzip                             |\n",
    "| Accept-Language | 客户端能够接受的语言                                         | Accept-Language: en,zh                                      |\n",
    "| Cache-Control   | 指定请求和响应遵循的缓存机制                                 | Cache-Control: no-cache                                     |\n",
    "| Connection      | 是否允许持久连接（HTTP 1.1默认允许）                         | Connection: close                                           |\n",
    "| Cookie          | 客户端发出请求时，会把保存在相同请求域名下的<br/>所有cookie值一起发送给web服务器 | Cookie: user=name; type=vip;                                |\n",
    "| Content-Length  | 请求正文的长度                                               | Content-Length: 520                                         |\n",
    "| Date            | 请求发送的日期和时间                                         | Date: Tue, 2 JUN 2020 12:15:36 GMT                          |\n",
    "| Host            | 指定请求的服务器的域名和端口号                               | Host: www.baidu.com                                         |\n",
    "| Referer         | 上次请求的URL                                                | Referer: https://www.baidu.com/s?wd=http&ie=utf-8           |\n",
    "| User-Agent      | 关于客户端的一些信息                                         | User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 请求正文\n",
    "\n",
    "- GET请求的信息包含在URL之中，因此不包含请求正文\n",
    "- POST请求的信息则是包含在正文之中的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 示例\n",
    "\n",
    "```\n",
    "GET / HTTP/1.1\n",
    "Host: 47.100.5.4:9527\n",
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\n",
    "Upgrade-Insecure-Requests: 1\n",
    "Cookie: _p_ake_mcjuw_oxyuwk_ksnqjcl_skw_xk_ksms_plwkd_=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dpbl9rZXkiOiJsb2dpbnN1Y2Nlc3MifQ.XtW69g.2zbtCqYnQWMRq6iu0QU91HqxrII\n",
    "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1 Safari/605.1.15\n",
    "Accept-Language: zh-cn\n",
    "Accept-Encoding: gzip, deflate\n",
    "Connection: keep-alive\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.4.2 HTTP 响应\n",
    "\n",
    "\n",
    "#### HTTP响应格式\n",
    "<br>\n",
    "\n",
    "<center>\n",
    "<img src=\"./figures/fig11-9.png\" width=\"50%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 状态码\n",
    "\n",
    "- HTTP响应的状态码是以1、2、3、4或5开头的三位整数\n",
    "  - 1XX：提示信息，表示服务器收到请求，需要客户端继续执行操作\n",
    "  - 2XX：响应成功，请求被成功接收并处理\n",
    "  - 3XX：重定向\n",
    "  - 4XX：客户端错误，请求包含错误或无法完成请求\n",
    "  - 5XX：服务器错误"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 常见的状态码\n",
    "\n",
    "| 状态码 | 描述                  | 含义                 |\n",
    "| :----- | :-------------------- | :------------------- |\n",
    "| 200    | OK                    | 响应成功             |\n",
    "| 400    | Bad Request           | HTTP请求格式不正确   |\n",
    "| 404    | Not Found             | 文件不存在           |\n",
    "| 405    | Method Not Allowed    | 服务器不支持请求方法 |\n",
    "| 500    | Internal Server Error | 服务器内部错误       |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 响应头\n",
    "\n",
    "- 常用的响应头信息\n",
    "\n",
    "| Header           | 解释                   | 示例                                                     |\n",
    "| :--------------- | :--------------------- | :------------------------------------------------------- |\n",
    "| Allow            | 服务器允许的请求方法   | Allow: GET, POST, HEAD                                   |\n",
    "| Content-Encoding | 响应正文的压缩编码类型 | Content-Encoding: gzip                                   |\n",
    "| Content-Language | 响应正文的语言         | Content-Language: en,zh                                  |\n",
    "| Content-Length   | 响应正文的长度         | Content-Length: 263                                      |\n",
    "| Content-Type     | 响应正文内容的MIME类型 | Content-Type: text/html; charset=utf-8                   |\n",
    "| Date             | 响应时间               | Date: Tue, 2 JUN 2020 12:15:36 GMT                       |\n",
    "| Expires          | 响应过期不再缓存的时间 | Expires: WED, 3 JUN 2020 12:15:36 GMT                    |\n",
    "| Last-Modified    | 请求资源的最后修改时间 | Last-Modified: Tue, 2 JUN 2020 12:15:36 GMT              |\n",
    "| Location         | 重定向的URL            | Location: https://www.baidu.com                          |\n",
    "| refresh          | 重定向后的页刷新时间   | Refresh: 5; url=https://www.baidu.com/s?wd=http&ie=utf-8 |\n",
    "| Server           | Web服务器软件名        | Server: Apache/1.3.27 (Unix) (Red-Hat/Linux)             |\n",
    "| Set-Cookie       | 设置 Cookie            | Set-Cookie: UserID=admin; Max-Age=3600; Version=1        |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### MIME协议\n",
    "\n",
    "- MIME\n",
    "  - Multipurpose Internet Mail Extension\n",
    "  - HTTP请求和响应正文的MIME类型用请求头或响应头部的Content-Type来指定\n",
    "  - MIME协议不是一个独立的协议，它可以用于各种应用层协议之上用于规范网络传输数据的格式\n",
    "    - 它与SMTP协议相结合，使得Email能够发送各种格式的文件\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- 常用MIME类型\n",
    " \n",
    "| 扩展名            | MIME类型                 |\n",
    "| :---------------- | :----------------------- |\n",
    "| avi               | video/x-msvideo          |\n",
    "| bin, exe          | application/octet-stream |\n",
    "| bmp               | image/bmp                |\n",
    "| css               | text/css                 |\n",
    "| gif               | image/gif                |\n",
    "| gz                | application/x-gzip       |\n",
    "| html, htm, c, txt | text/html                |\n",
    "| jpeg, jpg         | image/jpeg               |\n",
    "| js                | application/x-javascript |\n",
    "| mp3               | audio/mpeg               |\n",
    "| mpeg              | video/mpeg               |\n",
    "| pdf               | application/pdf          |\n",
    "| tar               | application/x-tar        |\n",
    "| txt, c            | text/plain               |\n",
    "| wav               | audio/x-wav              |\n",
    "| zip               | application/zip          |\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### 示例\n",
    "\n",
    "```\n",
    "HTTP/1.1 200 OK\n",
    "Content-Length: 2994\n",
    "Content-Type: text/html; charset=utf-8\n",
    "Date: Tue, 02 Jun 2020 03:31:21 GMT\n",
    "Server: waitress\n",
    "Set-Cookie: _p_ake_mcjuw_oxyuwk_ksnqjcl_skw_xk_ksms_plwkd_=eyJfcGVybWFuZW50Ijp0cnVlLCJsb2dpbl9rZXkiOiJsb2dpbnN1Y2Nlc3MifQ.XtXICQ.KtDU8d0d--nYNjz0C2XP-viv98o; Expires=Fri, 03-Jul-2020 03:31:21 GMT; HttpOnly; Path=/\n",
    "\n",
    "<!DOCTYPE html>\n",
    "<html>\n",
    "<head>\n",
    "    ...\n",
    "</head>\n",
    "<body>\n",
    "    ...\n",
    "</body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.4.3 HTTP 协议解析\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "# 例 11-4\n",
    "```\n",
    "\n",
    "```python\n",
    "from urllib.parse import parse_qs\n",
    "\n",
    "class Parser:\n",
    "    def __init__(self, content=b''):\n",
    "        self.reset()\n",
    "        self.append(content)\n",
    "\n",
    "    def reset(self):                            # 初始化/重置解析器\n",
    "        self.__dict__ = {}\n",
    "        self._buff = b''                        # 缓冲区\n",
    "        self.top = b''                          # 首行内容\n",
    "        self.head = b''                         # 头部内容\n",
    "        self.body = b''                         # 正文内容\n",
    "        self._head_ok = False                   # 头部是否接收完毕\n",
    "        self.head_dict = dict()                # 头部信息字典\n",
    "\n",
    "    def content(self):                          # 获取全部协议数据\n",
    "        return b''.join([self.top, b'\\r\\n', self.head,\n",
    "                         b'\\r\\n'*2, self.body]).decode('utf-8')\n",
    "\n",
    "    def append(self, recved):                   # 添加新的数据\n",
    "        if self._head_ok:\n",
    "            self.body = b''.join([self.body, recved])\n",
    "        else:\n",
    "            self._buff = b''.join([self._buff, recved])\n",
    "            if b'\\r\\n\\r\\n' in recved:\n",
    "                top_head, self.body = self._buff.split(b'\\r\\n\\r\\n', 1)\n",
    "                self.top, self.head = top_head.split(b'\\r\\n', 1)\n",
    "                self._head_ok = True\n",
    "                self._buff = b''\n",
    "                self._parse_top()\n",
    "                self._parse_head()\n",
    "\n",
    "    def _parse_top(self):                       # 解析协议首行\n",
    "        items = self.top.decode('utf-8').split(' ')\n",
    "        if items[0].startswith('HTTP'):         # 响应\n",
    "            self.type = 'RESPONSE'\n",
    "            self.version = items[0]             # 协议版本\n",
    "            self.resp_status = items[1]         # 响应状态码\n",
    "            self.resp_desc = items[2]           # 响应状态描述\n",
    "        else:\n",
    "            self.type = 'REQUEST'\n",
    "            self.req_method = items[0]          # 请求方法\n",
    "            self.req_url = items[1]             # 请求URL\n",
    "            self.query_string = ''\n",
    "            if '?' in self.req_url:\n",
    "                self.req_url, self.query_string = self.req_url.split('?')\n",
    "            self.version = items[2]             # 协议版本\n",
    "\n",
    "    def _parse_head(self):                      # 解析头部\n",
    "        items = self.head.decode('utf-8').split('\\r\\n')\n",
    "        for item in items:\n",
    "            key, value = self._cut_kv(item, ':')\n",
    "            if key == '':\n",
    "                continue\n",
    "            self.head_dict[key.strip().lower()] = value.strip()\n",
    "        if'Cookie' in self.head_dict:          # 解析cookie\n",
    "            cookies = dict()\n",
    "            for cookie in self.head_dict['Cookie'].split(';'):\n",
    "                key, value = self._cut_kv(cookie, '=')\n",
    "                if key == '':\n",
    "                    continue\n",
    "                cookies[key.strip()] = value.strip()\n",
    "            self.head_dict['Cookie'] = cookies\n",
    "\n",
    "    def _cut_kv(self, item, sym):\n",
    "        if sym in item:\n",
    "            key, value = item.split(sym, 1)\n",
    "        else:\n",
    "            key, value = item, True\n",
    "        return key.strip(), value\n",
    "\n",
    "    def cookie(self, key):                      # 获取cookie\n",
    "        cookies = self.head_dict.get('Cookie', False)\n",
    "        if not cookies:\n",
    "            return False\n",
    "        return cookies.get(key, False)\n",
    "\n",
    "    def __getattr__(self, key):                 # 获取头部数据\n",
    "        key = key.lower().replace('_', '-')\n",
    "        return self.head_dict.get(key, False)\n",
    "\n",
    "    def is_ok(self):                            # 数据接收是否完成\n",
    "        if not self._head_ok:\n",
    "            return False\n",
    "        if self.type == 'REQUEST' and self.req_method == 'GET':\n",
    "            return True\n",
    "        if len(self.body) >= int(self.content_length):\n",
    "            return True\n",
    "        return False\n",
    "\n",
    "    def req_params(self):                       # 解析请求参数\n",
    "        if self.type == 'RESPONSE':\n",
    "            return None\n",
    "        if self.req_method == 'GET':\n",
    "            return parse_qs(self.query_string)\n",
    "        elif self.req_method == 'POST':\n",
    "            return parse_qs(self.body.decode('utf-8'))\n",
    "        return None\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.5 Web 服务器的工作原理\n",
    "\n",
    "### 11.5.1 基于套接字的 Web 服务器端\n",
    "\n",
    "```python\n",
    "# 例 11-5\n",
    "```\n",
    "\n",
    "\n",
    "```python\n",
    "from socketserver import ThreadingTCPServer, StreamRequestHandler\n",
    "from httplib import Parser\n",
    "\n",
    "class TCPHandler(StreamRequestHandler):\n",
    "    def __init__(self, *args, **kwargs):\n",
    "        self.parser = Parser()\n",
    "        super().__init__(*args, **kwargs)\n",
    "\n",
    "    def handle(self):\n",
    "        while True:                               # 通信循环\n",
    "            data_recv = self.request.recv(1024)   # 接收数据\n",
    "            self.parser.append(data_recv)\n",
    "            if self.parser.is_ok():\n",
    "                break\n",
    "        print(f'{self.client_address} {self.parser.req_method} 请求')\n",
    "        if self.parser.req_method == 'GET':\n",
    "            send_data = self.do_GET()             # 处理GET请求\n",
    "        elif self.parser.req_method == 'POST':\n",
    "            send_data = self.do_POST()            # 处理POST请求\n",
    "        else:\n",
    "            send_data = self.do_error()           # 处理错误\n",
    "        self.request.sendall(send_data)           # 发送数据\n",
    "        self.request.close()\n",
    "\n",
    "    def do_GET(self):                             # 处理GET请求\n",
    "        html = self.load_file()\n",
    "        if html is None:\n",
    "            return self.do_error()\n",
    "        else:\n",
    "            body = html.encode('utf8')\n",
    "            head = self.make_head('200', len(body))\n",
    "            return head + body\n",
    "\n",
    "    def do_POST(self):                            # 处理POST请求\n",
    "        html = self.load_file()\n",
    "        if html is None:\n",
    "            return self.do_error()\n",
    "        else:\n",
    "            info = f'你的请求参数是：{self.parser.req_params()}'\n",
    "            html = html.replace('<span id=\"info\"/>', info)\n",
    "            body = html.encode('utf8')\n",
    "            head = self.make_head('200', len(body))\n",
    "            return head + body\n",
    "\n",
    "    def make_head(self, code, content_len):       # 生成响应头\n",
    "        head = b''\n",
    "        if code == '200':\n",
    "            head += b'HTTP/1.1 200 OK\\r\\n'\n",
    "        elif code == '404':\n",
    "            head += b'HTTP/1.1 404 Not Found\\r\\n'\n",
    "        head += f'Content-Length: {content_len}\\r\\n'.encode('utf-8')\n",
    "        head += b'Content-Type: text/html; charset=utf-8\\r\\n\\r\\n'\n",
    "        return head\n",
    "\n",
    "    def load_file(self):                          # 加载文件\n",
    "        try:\n",
    "            req_url = self.parser.req_url\n",
    "            path = '/index.html' if req_url == '/' else req_url\n",
    "            with open(f'.{path}') as f:\n",
    "                return f.read()\n",
    "        except Exception:\n",
    "            return None\n",
    "\n",
    "    def do_error(self):                           # 处理错误请求\n",
    "        return self.make_head('404', 18) + '404页面不存在'.encode('utf-8')\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    addr = ('127.0.0.1', 9000)\n",
    "    with ThreadingTCPServer(addr, TCPHandler) as server:\n",
    "        server.allow_reuse_address = True\n",
    "        print(f'HTTP服务器启动: http://{addr[0]}:{addr[1]}')\n",
    "        server.serve_forever()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.5.2 简单Web服务器\n",
    "\n",
    "```python\n",
    "# 例 11-6\n",
    "```\n",
    "\n",
    "```python\n",
    "from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer\n",
    "from urllib.parse import parse_qs\n",
    "\n",
    "class RequestHandler(BaseHTTPRequestHandler):\n",
    "    def do_GET(self):                             # 响应GET请求\n",
    "        html = self.load_file()\n",
    "        if html is not None:\n",
    "            self.make_response(html.encode('utf-8'))\n",
    "        else:\n",
    "            self.send_error(404, f'Page not found!')\n",
    "\n",
    "    def do_POST(self):                            # 响应POST请求\n",
    "        html = self.load_file()\n",
    "        if html is not None:\n",
    "            query = self.rfile.read(int(self.headers['content-length']))\n",
    "            params = parse_qs(query.decode(\"utf-8\"))\n",
    "            html = html.replace('<span id=\"info\"/>', str(params))\n",
    "            self.make_response(html.encode('utf-8'))\n",
    "        else:\n",
    "            self.send_error(404, f'Page not found!')\n",
    "\n",
    "    def load_file(self):                          # 加载文件\n",
    "        try:\n",
    "            path = '/index.html' if self.path == '/' else self.path\n",
    "            with open(f'.{path}') as f:\n",
    "                return f.read()\n",
    "        except Exception:\n",
    "            return None\n",
    "\n",
    "    def make_response(self, body):                # 构造HTTP响应\n",
    "        self.send_response(200)\n",
    "        self.send_header('Content-type', 'text/html')\n",
    "        self.send_header('Content-Length', len(body))\n",
    "        self.end_headers()\n",
    "        self.wfile.write(body)\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    addr = ('127.0.0.1', 9000)\n",
    "    with ThreadingHTTPServer(addr, RequestHandler) as server:\n",
    "        server.allow_reuse_address = True\n",
    "        print(f'服务器启动: http://{addr[0]}:{addr[1]}')\n",
    "        server.serve_forever()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.6 Web 客户端的工作原理\n",
    "\n",
    "- 注意：本小节代码的运行首先需要运行一个Web服务器\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.6.1 基于套接字的 Web 客户端"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- GET请求\n",
    "\n",
    "```python\n",
    "# 例 11-7\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HTTP请求: --------------------\n",
      "GET / HTTP/1.1\r\n",
      "host:127.0.0.1\r\n",
      "\r\n",
      "\n",
      "HTTP响应: --------------------\n",
      "HTTP/1.0 200 OK\r\n",
      "Server: BaseHTTP/0.6 Python/3.10.13\r\n",
      "Date: Fri, 08 Dec 2023 00:51:40 GMT\r\n",
      "Content-type: text/html\r\n",
      "Content-Length: 1675\r\n",
      "\r\n",
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import socket\n",
    "skt = socket.socket()\n",
    "host = '127.0.0.1'\n",
    "skt.connect((host, 9000))\n",
    "\n",
    "request = f\"GET / HTTP/1.1\\r\\nhost:{host}\\r\\n\\r\\n\"\n",
    "\n",
    "print('HTTP请求:', '-' * 20)\n",
    "print(request)\n",
    "\n",
    "request = request.encode('utf-8')\n",
    "skt.send(request)\n",
    "\n",
    "response = b''\n",
    "while True:\n",
    "    resp = skt.recv(1024)\n",
    "    if not resp:\n",
    "        break\n",
    "    response += resp\n",
    "skt.close()\n",
    "\n",
    "print('HTTP响应:', '-' * 20)\n",
    "print(response.decode('utf-8'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- POST请求\n",
    "\n",
    "```python\n",
    "# 例 11-8\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "HTTP请求: --------------------\n",
      "POST / HTTP/1.1\r\n",
      "Content-Type: application/x-www-form-urlencoded\r\n",
      "Content-Length: 64\r\n",
      "\r\n",
      "usertag=test&password=123456&code=print%28%22Hello%20Web%22%29\r\n",
      "\n",
      "HTTP响应: --------------------\n",
      "HTTP/1.1 200 OK\r\n",
      "Content-Length: 1675\r\n",
      "Content-Type: text/html; charset=utf-8\r\n",
      "\r\n",
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import socket\n",
    "from urllib.parse import quote\n",
    "\n",
    "skt = socket.socket()\n",
    "host = '127.0.0.1'\n",
    "skt.connect((host, 9000))\n",
    "\n",
    "code = '''print(\"Hello Web\")'''\n",
    "request_content = f'usertag=test&password=123456&code={quote(code)}\\r\\n'\n",
    "\n",
    "request = 'POST / HTTP/1.1\\r\\n'\n",
    "request += 'Content-Type: application/x-www-form-urlencoded\\r\\n'\n",
    "request += f'Content-Length: {len(request_content.encode(\"utf-8\"))}\\r\\n'\n",
    "request += '\\r\\n'\n",
    "request += request_content\n",
    "\n",
    "print('HTTP请求:', '-' * 20)\n",
    "print(request)\n",
    "\n",
    "request = request.encode('utf-8')\n",
    "skt.send(request)\n",
    "\n",
    "response = b''\n",
    "while True:\n",
    "    resp = skt.recv(1024)\n",
    "    if not resp:\n",
    "        break\n",
    "    response += resp\n",
    "skt.close()\n",
    "\n",
    "print('HTTP响应:', '-' * 20)\n",
    "print(response.decode('utf-8'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.6.2 基于 http.client 的 Web 客户端\n",
    "\n",
    "- GET请求\n",
    "\n",
    "```python\n",
    "# 例 11-9\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import http.client\n",
    "\n",
    "conn = http.client.HTTPConnection(\"127.0.0.1:9000\")\n",
    "conn.request(\"GET\", '/')\n",
    "resp = conn.getresponse()\n",
    "html = resp.read().decode(\"utf-8\")\n",
    "conn.close()\n",
    "print(html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- POST请求\n",
    "\n",
    "```python\n",
    "# 例 11-10\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import http.client\n",
    "from urllib.parse import urlencode\n",
    "conn = http.client.HTTPConnection(\"127.0.0.1:9000\")\n",
    "\n",
    "post_data = {\n",
    "    \"usertag\": \"test\",\n",
    "    \"password\": '123456',\n",
    "    'code': \"print('Hello Web')\"\n",
    "}\n",
    "head = {\n",
    "    'Content-Type': 'application/x-www-form-urlencoded'\n",
    "}\n",
    "post_data = urlencode(post_data)\n",
    "conn.request(method=\"POST\", url='/', body=post_data.encode('utf-8'), headers=head)\n",
    "resp = conn.getresponse()\n",
    "html = resp.read().decode(\"utf-8\")\n",
    "conn.close()\n",
    "print(html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### 11.6.3 urllib.request与requests\n",
    "\n",
    "####  GET请求\n",
    "\n",
    "- `urllib.request`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "from urllib import request\n",
    "\n",
    "resp = request.urlopen(\"http://127.0.0.1:9000/\")\n",
    "html = resp.read().decode(\"utf-8\")\n",
    "print(html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `requests`\n",
    "   - `pip install requests`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "\n",
    "resp = requests.get(\"http://127.0.0.1:9000/\")\n",
    "print(resp.text)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "#### POST请求\n",
    "\n",
    "- `urllib.request`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "from urllib import request\n",
    "from urllib.parse import urlencode\n",
    "\n",
    "post_data = {\n",
    "    \"usertag\": \"test\",\n",
    "    \"password\": '123456',\n",
    "    'code': \"print('Hello Web')\"\n",
    "}\n",
    "head = {\n",
    "    'Content-Type': 'application/x-www-form-urlencoded'\n",
    "}\n",
    "\n",
    "requ = request.Request(\"http://127.0.0.1:9000/\", data=urlencode(post_data).encode('utf-8'), headers=head)\n",
    "\n",
    "resp = request.urlopen(requ)\n",
    "print(resp.read().decode(\"utf-8\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- `requests`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<!DOCTYPE html>\n",
      "<html>\n",
      "\n",
      "<head>\n",
      "    <meta charset=\"UTF-8\">\n",
      "    <title>Python编程</title>\n",
      "    <style>\n",
      "        .continer {\n",
      "            margin: 0 auto;\n",
      "            width: 80%;\n",
      "        }\n",
      "\n",
      "        .box {\n",
      "            margin: 0 auto;\n",
      "            width: 100%;\n",
      "            text-align: center;\n",
      "        }\n",
      "    </style>\n",
      "    <script type=\"text/javascript\">\n",
      "        function clear_code(id) {\n",
      "            if (document.getElementById(id).value == \"代码拷到这里！\") {\n",
      "                document.getElementById(id).value = \"\";\n",
      "            }\n",
      "        }\n",
      "    </script>\n",
      "</head>\n",
      "\n",
      "<body>\n",
      "    <div class=\"continer\">\n",
      "        <div class=\"box\">\n",
      "            <h1>课后作业提交</h1>\n",
      "            <form action=\"/\" method=\"post\" accept-charset=\"utf-8\">\n",
      "                <div>\n",
      "                    <span>学号：</span>\n",
      "                    <input type=\"text\" name=\"usertag\" style=\"width: 20%;\" />\n",
      "                    <span style=\"width: 5%; display:inline-block\"></span>\n",
      "                    <span>密码：</span>\n",
      "                    <input type=\"password\" name=\"password\" style=\"width: 20%;\" />\n",
      "                </div><br>\n",
      "                <div>\n",
      "                    <textarea name=\"code\" id=\"codebox\" style=\"width:100%;overflow-y:scroll;overflow:scroll;\" rows=\"10\"\n",
      "                        wrap=\"off\" onclick=\"clear_code(this.id)\">代码拷到这里！</textarea>\n",
      "                </div><br>\n",
      "                <div style=\"margin: 0 auto; text-align: center;\">\n",
      "                    <input type=\"submit\" value=\"提交代码\" style=\"width:100px; line-height:99px;display:inline-block\" />\n",
      "                </div>\n",
      "            </form>\n",
      "        </div>\n",
      "        <div><span id=\"info\" /></div>\n",
      "    </div>\n",
      "</body>\n",
      "\n",
      "</html>\n"
     ]
    }
   ],
   "source": [
    "import requests\n",
    "from urllib.parse import urlencode\n",
    "\n",
    "post_data = {\n",
    "    \"usertag\": \"test\",\n",
    "    \"password\": '123456',\n",
    "    'code': \"print('Hello Web')\"\n",
    "}\n",
    "head = {\n",
    "    'Content-Type': 'application/x-www-form-urlencoded'\n",
    "}\n",
    "res = requests.post(\"http://127.0.0.1:9000/\", data=urlencode(post_data), headers=head)\n",
    "print(res.text)  # 转为字典格式"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 11.7 WebSocket 协议 *\n",
    "\n",
    "- HTTP协议的缺点\n",
    "  - 服务器不能主动向客户端发送消息\n",
    "\n",
    "### 11.7.1 WebSocket 的工作过程\n",
    "\n",
    "- 握手\n",
    "  - 基于HTTP协议\n",
    "  - 协议升级\n",
    "- 通信\n",
    "  - 双向通信\n",
    "  \n",
    "<center>\n",
    "    <img src=\"./figures/fig11-10.png\" width=\"40%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.7.2 握手"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- WebSocket 握手请求\n",
    "  - 重要的几个请求头\n",
    "    - `Connection`: 取值必须为Upgrade表示客户端希望升级协议\n",
    "    - `Upgrade`: 取值必须为websocket表示客户端希望升级的协议为 WebSocket 协议\n",
    "    - `Sec-Websocket-Key`: 客户端随机生成的一串字符\n",
    "    - `Sec-WebSocket-Version`: WebSocket 协议版本，RFC6455 规定该值必须为 13\n",
    "\n",
    "<br>\n",
    "\n",
    "- 示例\n",
    "\n",
    "```bash\n",
    "GET / HTTP/1.1\n",
    "Connection:Upgrade\n",
    "Upgrade:websocket\n",
    "Host: 127.0.0.1:9000\n",
    "Origin: null Sec-Websocket-Key:wxUqKAAwIh+5ZR19JCA+jw== Sec-WebSocket-Version:13\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- WebSocket 握手响应\n",
    "  - 响应代码为 101，表示服务器同意客户端协议转换请求\n",
    "  - 握手响应中同样包含了特殊的响应头 和取值，主要有三个`Connection`、`Upgrade`和`Sec-WebSocket-Accept`\n",
    "    - `Connection` 和`Upgrade`与请求头一致\n",
    "- `Sec-WebSocket-Accept`\n",
    "  - 将请求中`Sec-Websocket-Key`的值与“Magic String”拼接\n",
    "    - 258EAFA5-E914-47DA-95CA-C5AB0DC85B11\n",
    "  - SHA-1 编码\n",
    "  - Base64 编码\n",
    "\n",
    "<br>\n",
    "\n",
    "- 示例\n",
    "\n",
    "```bash\n",
    "HTTP/1.1 101 Switching Protocols\n",
    "Upgrade: websocket\n",
    "Connection: Upgrade\n",
    "Sec-WebSocket-Accept: 2k2O9g8lUREQjApXs63/dmxhH7U=\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "- WebSocket 握手的请求解析与响应构造\n",
    "\n",
    "```python\n",
    "# 例 11-13\n",
    "```\n",
    "\n",
    "```python\n",
    "from base64 import b64encode\n",
    "from hashlib import sha1\n",
    "\n",
    "def hand_shake(conn, http_parser):               # 握手\n",
    "    http_parser.reset()\n",
    "    while True:                                  # 通信循环\n",
    "        data_recv = conn.recv(1024)              # 接收数据\n",
    "        http_parser.append(data_recv)            # 解析HTTP请求\n",
    "        if http_parser.is_ok(): break\n",
    "    send_data = handshake_resp(http_parser.sec_websocket_key)\n",
    "    conn.send(send_data.encode('utf-8'))\n",
    "\n",
    "def handshake_resp(key):                         # 构造握手的HTTP响应\n",
    "    magic_string = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' # Magic String   \n",
    "    hashed = sha1(key.encode('utf-8') + magic_string.encode('utf-8'))\n",
    "    resp_key = b64encode(hashed.digest()).strip().decode('utf-8')\n",
    "    response = [\"HTTP/1.1 101 Switching Protocols\\r\\n\",\n",
    "                \"Upgrade: websocket\\r\\n\", \n",
    "                \"Connection: Upgrade\\r\\n\",\n",
    "                f\"Sec-WebSocket-Accept: {resp_key}\\r\\n\", \"\\r\\n\"]\n",
    "    return ''.join(response)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.7.3 WebSocket 协议解析\n",
    "\n",
    "- 协议标识为ws\n",
    "  - 例： ws://www.test.com/websocket\n",
    "- WebSocket 协议中数据传输的单位为帧(Frame)，每帧能够携带的最大数据量为 264 − 1 字节\n",
    "\n",
    "<br>\n",
    "<center>\n",
    "    <img src=\"./figures/fig11-11.png\" width=\"50%\"/>\n",
    "</center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "### 11.7.4 WebSocket 服务器\n",
    "\n",
    "- 服务器端\n",
    "\n",
    "```python\n",
    "# 例 11-16\n",
    "```\n",
    "\n",
    "```python\n",
    "from socketserver import ThreadingTCPServer, StreamRequestHandler\n",
    "from httplib import Parser\n",
    "from websocketlib import WSParser, WSBuilder, hand_shake\n",
    "\n",
    "class TCPHandler(StreamRequestHandler):\n",
    "    def __init__(self, *args, **kwargs):\n",
    "        self.http_parser = Parser()                    # HTTP解析器\n",
    "        self.ws_parser = WSParser()                    # WebSocket解析器\n",
    "        self.ws_builder = WSBuilder()                  # WebSocket构造器\n",
    "        super().__init__(*args, **kwargs)\n",
    "\n",
    "    def handle(self):\n",
    "        hand_shake(self.request, self.http_parser)     # 握手\n",
    "        self.ws_parser.init(self.request)\n",
    "        self.ws_builder.init(self.request)\n",
    "        i = 1\n",
    "        while True:\n",
    "            self.ws_parser.parse()                     # 接收并解析WS消息\n",
    "            print(f'接收到客户端消息：{self.ws_parser.data}')\n",
    "            send_data = f'msg-第{i:>2}次交互'       \n",
    "            send_data = send_data.encode('utf-8')\n",
    "            self.ws_builder.build(text_data=send_data) # 发送第一条消息\n",
    "            send_data = f'msg-服务器收到：{self.ws_parser.data}'\n",
    "            if self.ws_parser.data == '':\n",
    "                send_data = '服务器收到空消息，连接关闭！'\n",
    "            send_data = send_data.encode('utf-8')\n",
    "            self.ws_builder.build(text_data=send_data) # 发送第二条消息\n",
    "            if self.ws_parser.data == '': break\n",
    "            i += 1\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    addr = ('127.0.0.1', 9000)\n",
    "    with ThreadingTCPServer(addr, TCPHandler) as server:\n",
    "        server.allow_reuse_address = True\n",
    "        print(f'WebSocket服务器启动: ws://{addr[0]}:{addr[1]}')\n",
    "        server.serve_forever()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 基于网页的客户端\n",
    "\n",
    "```python\n",
    "# 例 11-17\n",
    "```\n",
    "\n",
    "```html\n",
    "<html>\n",
    "<head>\n",
    "    <title>WebSocket客户端</title>\n",
    "    <script type=\"text/javascript\">\n",
    "        var ws_client;\n",
    "        ws_client = new WebSocket(\"ws://127.0.0.1:9000/\");\n",
    "        ws_client.onopen = function () {           // 握手成功回调函数\n",
    "            print('--- 握手成功！')\n",
    "        };\n",
    "        ws_client.onmessage = function (e) {       // 接收消息回调函数\n",
    "            print(\"<<< 收到消息: \" + e.data);\n",
    "        };\n",
    "        ws_client.onerror = function(e) {          // 运行错误回调函数\n",
    "            print(e.value);\n",
    "        };\n",
    "        function send_msg() {                      // 发送消息\n",
    "            print(\"------------------\")\n",
    "            var msg = document.getElementById(\"input\");\n",
    "            ws_client.send(msg.value);\n",
    "            print(\">>> 发送消息：\" + msg.value);\n",
    "            msg.value = \"\";\n",
    "            msg.focus();\n",
    "        }\n",
    "        function print(str) {                      // 输出消息\n",
    "            var info = document.getElementById(\"info\");\n",
    "            info.innerHTML = str + \"<br>\" + info.innerHTML;\n",
    "        }\n",
    "    </script>\n",
    "</head>\n",
    "<body>\n",
    "    <input type=\"text\" id=\"input\">\n",
    "    <button onclick=\"send_msg()\">发送消息</button>\n",
    "    <div id=\"info\"></div>\n",
    "</body>\n",
    "</html>\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "    <img src=\"./figures/fig11-12.png\" width=\"60%\"/>\n",
    "</center>"
   ]
  }
 ],
 "metadata": {
  "author": "liuchen",
  "celltoolbar": "幻灯片",
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  },
  "rise": {
   "enable_chalkboard": true,
   "footer": "",
   "progress": true,
   "scroll": true,
   "slideNumber": true
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": false,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "url": "https://github.com/hitlic/python_book"
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
